package com.swak.wechat;

import java.io.ByteArrayInputStream;
import java.security.KeyStore;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.swak.http.HttpService;
import com.swak.incrementer.UUIdGenerator;
import com.swak.utils.JaxbMapper;
import com.swak.utils.Maps;
import com.swak.utils.ssl.SslContexts;
import com.swak.wechat.codec.SignUtils;
import com.swak.wechat.pay.MchOrderquery;
import com.swak.wechat.pay.MchPayment;
import com.swak.wechat.pay.Order;
import com.swak.wechat.pay.Refundorder;
import com.swak.wechat.pay.Refundquery;
import com.swak.wechat.pay.SandboxSignKey;
import com.swak.wechat.pay.Unifiedorder;

import io.netty.handler.ssl.SslContext;
import lombok.Getter;

/**
 * Wechat 服务
 *
 * @author: lifeng
 * @date: 2020/4/1 11:38
 */
public abstract class AbstractMchService {

	protected static final Logger logger = LoggerFactory.getLogger(AbstractMchService.class);

	@Getter
	protected static class Wrap {
		private final String md5;
		private final HttpService service;

		Wrap(String md5, HttpService service) {
			this.md5 = md5;
			this.service = service;
		}
	}

	/**
	 * Http 服务
	 *
	 * @return http 服务
	 */
	public abstract HttpService getHttpService();

	/**
	 * SSl 服务: MchId -> HttpService
	 */
	private Map<String, Wrap> sslHttpServices = Maps.newConcurrentMap();

	/**
	 * 获取 SSl 服务， 如果有多个wechat 的ssl 需要处理可以根据  管理多个
	 *
	 * @param app app
	 * @return http ssl 服务
	 */
	public HttpService getSslHttpService(MchConfig app) {
		Wrap wrap = sslHttpServices.get(app.getMchId());
		if (wrap == null || !app.getSslCertDataMD5().equals(wrap.getMd5())) {
			synchronized (sslHttpServices) {
				wrap = sslHttpServices.get(app.getMchId());
				if (wrap == null || !app.getSslCertDataMD5().equals(wrap.getMd5())) {
					this.sslHttpServices.remove(app.getMchId());
					wrap = this.sslHttpServices.computeIfAbsent(app.getMchId(), key -> {
						Wrap _wrap = new Wrap(app.getSslCertDataMD5(),
								this.getHttpService().sslConfig(this.getSslContext(app)));
						return _wrap;
					});
				}
			}
		}
		return wrap.getService();
	}

	private SslContext getSslContext(MchConfig app) {
		try (ByteArrayInputStream in = new ByteArrayInputStream(app.getSslCertData())) {
			KeyStore keystore = KeyStore.getInstance("PKCS12");
			char[] partnerId2charArray = app.getMchId().toCharArray();
			keystore.load(in, partnerId2charArray);
			return SslContexts.forClient().useKeyStore(keystore, partnerId2charArray).build();
		} catch (Exception e) {
			logger.error("Set SSL error: ", e);
		}
		return null;
	}

	// ==========================================================
	// 微信支付： 下单 -> 查询订单 -> 退款 -> 退款查询
	// ==========================================================

	/**
	 * 统一下单
	 *
	 * @param app          app
	 * @param unifiedorder 数据
	 * @return 异步结果
	 */
	public CompletableFuture<Map<String, Object>> unifiedorder(MchConfig app, Unifiedorder unifiedorder) {
		CompletableFuture<String> future;
		if (app.isUseSandbox()) {
			unifiedorder.setSign_type(Constants.MD5);
			future = getSandboxSignKey(app, unifiedorder.getSign_type()).thenApply(res -> {
				app.sandboxSetMchKey(res);
				return Constants.SANDBOX_UNIFIEDORDER_URL_SUFFIX;
			});
		} else {
			unifiedorder.setSign_type(app.getSignType());
			future = CompletableFuture.completedFuture(Constants.UNIFIEDORDER_URL_SUFFIX);
		}
		return future.thenCompose(res -> this.orderFuture(app, unifiedorder, res))
				.thenApply(res -> app.process(res, unifiedorder.getSign_type()))//
				.thenApply(res -> {
					String nativeTradeType = "NATIVE";
					if (nativeTradeType.equals(unifiedorder.getTrade_type())) {
						return res;
					}
					String signed = SignUtils.generateJsPayJson(unifiedorder.getNonce_str(),
							String.valueOf(res.get("prepay_id")), unifiedorder.getAppid(), unifiedorder.getSign_type(),
							app.getMchKey());
					res.put("jsPayJson", signed);
					return res;
				});
	}

	/**
	 * 查询订单
	 *
	 * @param app           app
	 * @param mchOrderquery 数据
	 * @return 异步结果
	 */
	public CompletableFuture<Map<String, Object>> queryOrder(MchConfig app, MchOrderquery mchOrderquery) {
		CompletableFuture<String> future;
		if (app.isUseSandbox()) {
			mchOrderquery.setSign_type(Constants.MD5);
			future = getSandboxSignKey(app, mchOrderquery.getSign_type()).thenApply(res -> {
				app.sandboxSetMchKey(res);
				return Constants.SANDBOX_ORDERQUERY_URL_SUFFIX;
			});
		} else {
			mchOrderquery.setSign_type(app.getSignType());
			future = CompletableFuture.completedFuture(Constants.ORDERQUERY_URL_SUFFIX);
		}
		return future.thenCompose(res -> this.orderFuture(app, mchOrderquery, res))
				.thenApply(res -> app.process(res, mchOrderquery.getSign_type()));
	}

	/**
	 * 订单操作
	 *
	 * @param app   app
	 * @param order 订单
	 * @param res   操作地址
	 * @return 异步结果
	 */
	private CompletableFuture<String> orderFuture(MchConfig app, Order order, String res) {
		String url = "https://" + Constants.MCH_URI_DOMAIN_API + res;
		order.checkAndSign(app);
		String reqBody = JaxbMapper.toXml(order);
		return this.request(this.getHttpService(), app, url, reqBody);
	}

	/**
	 * 退款
	 *
	 * @param app    app
	 * @param refund 数据
	 * @return 异步结果
	 */
	public CompletableFuture<Map<String, Object>> refundOrder(MchConfig app, Refundorder refund) {
		CompletableFuture<String> future;
		if (app.isUseSandbox()) {
			refund.setSign_type(Constants.MD5);
			future = getSandboxSignKey(app, refund.getSign_type()).thenApply(res -> {
				app.sandboxSetMchKey(res);
				return Constants.SANDBOX_REFUND_URL_SUFFIX;
			});
		} else {
			refund.setSign_type(app.getSignType());
			future = CompletableFuture.completedFuture(Constants.REFUND_URL_SUFFIX);
		}
		return future.thenCompose(res -> {
			String url = "https://" + Constants.MCH_URI_DOMAIN_API + res;
			refund.checkAndSign(app);
			String reqBody = JaxbMapper.toXml(refund);
			return this.request(getSslHttpService(app), app, url, reqBody);
		}).thenApply(res -> app.process(res, refund.getSign_type()));
	}

	/**
	 * 退款申请查询
	 *
	 * @param app   app
	 * @param query 数据
	 * @return 异步结果
	 */
	public CompletableFuture<Map<String, Object>> refundQuery(MchConfig app, Refundquery query) {
		CompletableFuture<String> future;
		if (app.isUseSandbox()) {
			query.setSign_type(Constants.MD5);
			future = getSandboxSignKey(app, query.getSign_type()).thenApply(res -> {
				app.sandboxSetMchKey(res);
				return Constants.SANDBOX_REFUNDQUERY_URL_SUFFIX;
			});
		} else {
			query.setSign_type(app.getSignType());
			future = CompletableFuture.completedFuture(Constants.REFUNDQUERY_URL_SUFFIX);
		}
		return future.thenCompose(res -> {
			String url = "https://" + Constants.MCH_URI_DOMAIN_API + res;
			query.checkAndSign(app);
			String reqBody = JaxbMapper.toXml(query);
			return this.request(getSslHttpService(app), app, url, reqBody);
		}).thenApply(res -> app.process(res, query.getSign_type()));
	}

	/**
	 * 获得沙箱测试密钥
	 *
	 * @param app      app
	 * @param signType 签名类型
	 * @return 异步结果
	 */
	private CompletableFuture<String> getSandboxSignKey(MchConfig app, String signType) {
		SandboxSignKey data = new SandboxSignKey();
		data.setMch_id(app.getMchId());
		data.setNonce_str(UUIdGenerator.uuid());
		data.setSign_type(signType);
		data.setSign(SignUtils.generateSign(data, data.getSign_type(), app.getMchKey()));
		String reqBody = JaxbMapper.toXml(data);
		String url = "https://" + Constants.MCH_URI_DOMAIN_API + Constants.SANDBOX_GET_SIGNKEY_SUFFIX;
		return this.request(this.getHttpService(), app, url, reqBody).thenApply(res -> {
			Map<String, Object> maps = Maps.fromXml(res);
			return String.valueOf(maps.get("sandbox_signkey"));
		});
	}

	// ==========================================================
	// 微信结算： 发红包 - 发现金
	// ==========================================================

	/**
	 * 发现金
	 *
	 * @param app        app
	 * @param mchPayment mch支付
	 * @return 异步结果
	 */
	public CompletableFuture<Map<String, Object>> sendamount(MchConfig app, MchPayment mchPayment) {
		CompletableFuture<String> future = CompletableFuture.completedFuture(Constants.MMPAYMKTTRANSFERS_URL_SUFFIX);
		return future.thenCompose(res -> {
			String url = "https://" + Constants.MCH_URI_DOMAIN_API + res;
			mchPayment.checkAndSign(app);
			String reqBody = JaxbMapper.toXml(mchPayment);
			return this.request(this.getHttpService(), app, url, reqBody);
		}).thenApply(Maps::fromXml);
	}

	// ==========================================================
	// 发起请求
	// ==========================================================

	/**
	 * 请求
	 *
	 * @param client 客户端
	 * @param url    地址
	 * @param data   数据
	 * @return 异步结果
	 */
	public CompletableFuture<String> request(HttpService client, MchConfig app, String url, String data) {
		return client.post().textResult().setUrl(url).setHeader("Content-Type", "text/xml")
				.setHeader("User-Agent", "wxpay sdk java v1.0 " + app.getMchId())
				.setRequestTimeout(app.getHttpConnectTimeoutMs()).setReadTimeout(app.getHttpReadTimeoutMs())
				.setBody(data).future();
	}
}
